/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file parser.rl
 * PyHP Code Parser
 */
 
#include <stdio.h>
#include <sys/stat.h>

#include <apr_file_io.h>
#include <apr_strings.h>
#include <apr_mmap.h>
#include <apr_tables.h>
#include <libgen.h>

#include "parse.h"
#include "mod_pyhp.h"
#include "logging.h"
#include "config.h"

enum block_type { NONE, PYHP_INVALID, PYHP_CODE, PYHP_INCLUDE  };

char *trim(char *s, unsigned len)
{
    char *param_begin=s;
    char *param_end=s+len+1;

    while(isspace(*param_begin))
        ++param_begin;
    while(isspace(*(param_end-1)))
        --param_end;
        
    s=param_begin;
    s[param_end-param_begin] = '\0';
    
    return s;
}

char const * const tag_to_string(enum block_type t)
{
    static char const * const unknown="Unknown";
    static char const * const pyhp="<?pyhp?>";
    static char const * const include="<?include?>";
    static char const * const huh="huh?";
    
    if (t < PYHP_CODE)
        return unknown;
    else if (t == PYHP_CODE)
        return pyhp;
    else if (t == PYHP_INCLUDE)
        return include;
    else
        return huh;
}
 
int count_spaces(char * s)
{
    int space_count=0;
    
    while (*s == '\n')
        ++s;
    
    while (isspace(*s)) {
        ++space_count;
        ++s;
    }
    return space_count;
}
 
 %%{
        machine pyhp;
        
        action parse_code 
        {   
            if (inblock == NONE) {
                pyhp_logf(r, PyHP_ERR, "[%s:%d] -> Error while parsing Code block", filename, line_count);
            }
            else {
                char *s=apr_pstrndup(r->pool, code_mark, fpc-code_mark-2);
                       
                if (inblock == PYHP_INCLUDE) {
                    struct stat file_info;
                    apr_file_t *inc_file;
                    apr_mmap_t *inc_file_content;
                    unsigned long long remanining_size = content->size - (fpc - (const char*)content->mm);
                    
                    char *filename = trim(s, fpc-code_mark-2);
                    
                    /* build full path */
                    if (filename[0] != '/')
                        filename = apr_pstrcat(r->pool, root, "/", filename, NULL);
                        
                    result = pyhp_parse_file_do(pydata, r, stack, filename, result);
                }
                else if (inblock == PYHP_CODE) {
                    int spaces=count_spaces(s);
                    char *strtok_state=NULL;
                    char *stok=s;
                    char *line;
                    int shift=0;
                    
                    //pyhp_logf(r, PyHP_DEBUG, "[%s:%d] -> Found code block with indentation %d", filename, block_line_count+shift, spaces);
                    
                    while( (line = strtok_r(stok, "\n", &strtok_state)) ) {
                        ++shift;
                        int line_spaces=0;
                        
                        while(line_spaces < spaces) {
                            if(isspace(*line)) {
                                ++line_spaces;
                                ++line;
                            }
                            else {
                                pyhp_logf(r, PyHP_WARN, "[%s:%d] -> Found line with less indent than code block (%d instead of %d)", filename, block_line_count+shift, line_spaces, spaces);
                                break;
                            }                                
                        }
                        
                        stok=NULL;
                        result=apr_pstrcat(r->pool, result, line, "\n", NULL);
                    }
                }
                    
                inblock=NONE;
            }
            
            html_mark=fpc;
        }
                       
        action parse_html 
        {
            code_mark=fpc;
            
            if (prevblock >= PYHP_CODE) {
                pyhp_logf(r, PyHP_ERR, "[%s:%d] -> Unclosed Tag %s", filename, line_count, tag_to_string(prevblock));
            }
            else {
                int tag_len = 0;
            
                if (inblock == PYHP_CODE)
                    tag_len = 6;
                else if (inblock == PYHP_INCLUDE)
                    tag_len = 9;
                 
                char *s=apr_pstrndup(r->pool, html_mark, fpc-html_mark-tag_len);
                result=apr_pstrcat(r->pool, result, "\npyhp.write('''", s, "''')\n", NULL);
            }
        }
        
        action parse_final
        {
            if (inblock != NONE)
                pyhp_logf(r, PyHP_ERR, "[%s:EOF] -> Unclosed Tag %s", filename, tag_to_string(inblock));
            else {
                char *s=apr_pstrndup(r->pool, html_mark, fpc-html_mark); 
                result=apr_pstrcat(r->pool, result, "\npyhp.write('''", s, "''')\n", NULL); 
            }   
        }
        
        action enter_pyhp
        {
            prevblock=inblock;
            inblock=PYHP_CODE;
            block_line_count=line_count;
        }
        
        action enter_include
        {
            prevblock=inblock;
            inblock=PYHP_INCLUDE;
            block_line_count=line_count;
        }
   
        new_line="\n" %{ line_count++; };
   
        pyhp_endtag="?>" %parse_code;
        
        pyhp_begin_block="<?pyhp" %enter_pyhp %parse_html; 
        pyhp_begin_include="<?include" %enter_include %parse_html;

        pyhp_block=pyhp_begin_block . any+ . pyhp_endtag;
        pyhp_include=pyhp_begin_include . any+ . pyhp_endtag;
        
        code=(pyhp_block|pyhp_include|new_line|any+)* 0 @parse_final;
        
        parser:=code;
}%%

%%write data;

char *pyhp_parse_file_do(PyHPData *pydata, request_rec *r, apr_table_t *stack, char const *filename, char *result)
{
    apr_file_t *file;
    apr_mmap_t *content;
    struct stat info;
    apr_status_t rv;
    
    int cs;
    char const *p=0;
    char const *pe=0;
   
    char const *code_mark=0;
    char const *html_mark=0;
  
    unsigned long line_count=1;
    unsigned long block_line_count=line_count;
    
    enum block_type inblock=NONE;
    enum block_type prevblock=NONE;
    
    char *root = dirname(apr_pstrdup(r->pool,filename));
    
    %%write init;
    
    if (apr_file_open(&file, filename, APR_READ, APR_OS_DEFAULT, r->pool) != APR_SUCCESS)
        return 0;
        
    if (apr_table_get(stack, filename))
        return NULL;

    apr_table_set(stack, filename, "1");

    lstat(filename, &info);

    if ((rv = apr_mmap_create(&content, file, 0, info.st_size, APR_MMAP_READ, r->pool)) != APR_SUCCESS) {
        char buf[1024];
        pyhp_logf(r, PyHP_ERR, "%s :: %s size %d", apr_strerror(rv, buf, 1024), filename, info.st_size);
        return NULL;
    }
      
    PyObject *sys_mdict = PyModule_GetDict(pydata->sys_module);
    PyObject *path = PyDict_GetItemString(sys_mdict, "path");
    PyObject *py_dir = PyString_FromString(root);
    
    while (PySequence_Count(path, py_dir) != 0) {
        int pos = PySequence_Index(path, py_dir);
        PySequence_DelItem(path, pos);
    }
  
    if (!PyObject_CallMethod(path, "append", "s", root))
        pyhp_logf(r, PyHP_ERR, "Error while appending %s to path", root);
  
    /* Set up the parser */
    p=content->mm;
    pe= content->mm + content->size + 1;
    html_mark=content->mm;
  
    %%write exec;

    if ( (cs == pyhp_error) || (cs < pyhp_first_final) )
        return NULL;
        
    apr_mmap_delete(content);
    apr_file_close(file);

    return result;
}

char *pyhp_parse_file(PyHPData *pydata, request_rec *r, const char *filename)
{
    apr_table_t *include_stack = apr_table_make(r->pool, 10);
    char *result="";
    
    return pyhp_parse_file_do(pydata, r, include_stack, filename, result);
}